/**********
This library is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 3 of the License, or (at your
option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)

This library is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
more details.

You should have received a copy of the GNU Lesser General Public License
along with this library; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
**********/
// "liveMedia"
// Copyright (c) 1996-2018 Live Networks, Inc.  All rights reserved.
// A class encapsulating the state of a MP3 stream
// Implementation

#include "MP3StreamState.hh"
#include "include/InputFile.hh"
#include "../groupsock/include/GroupsockHelper.hh"

#if defined(__WIN32__) || defined(_WIN32)
#define snprintf _snprintf
#if _MSC_VER >= 1400 // 1400 == vs2005
#define fileno _fileno
#endif
#endif

#define MILLION 1000000

MP3StreamState::MP3StreamState(UsageEnvironment &env)
        : fEnv(env), fFid(NULL), fPresentationTimeScale(1) {
}

MP3StreamState::~MP3StreamState() {
    // Close our open file or socket:
    if (fFid != NULL && fFid != stdin) {
        if (fFidIsReallyASocket) {
            intptr_t fid_long = (intptr_t) fFid;
            closeSocket((int) fid_long);
        } else {
            CloseInputFile(fFid);
        }
    }
}

void MP3StreamState::assignStream(FILE *fid, unsigned fileSize) {
    fFid = fid;

    if (fileSize == (unsigned) (-1)) { /*HACK#####*/
        fFidIsReallyASocket = 1;
        fFileSize = 0;
    } else {
        fFidIsReallyASocket = 0;
        fFileSize = fileSize;
    }
    fNumFramesInFile = 0; // until we know otherwise
    fIsVBR = fHasXingTOC = False; // ditto

    // Set the first frame's 'presentation time' to the current wall time:
    gettimeofday(&fNextFramePresentationTime, NULL);
}

struct timeval MP3StreamState::currentFramePlayTime() const {
    unsigned const numSamples = 1152;
    unsigned const freq = fr().samplingFreq * (1 + fr().isMPEG2);

    // result is numSamples/freq
    unsigned const uSeconds
            = ((numSamples * 2 * MILLION) / freq + 1) / 2; // rounds to nearest integer

    struct timeval result;
    result.tv_sec = uSeconds / MILLION;
    result.tv_usec = uSeconds % MILLION;
    return result;
}

float MP3StreamState::filePlayTime() const {
    unsigned numFramesInFile = fNumFramesInFile;
    if (numFramesInFile == 0) {
        // Estimate the number of frames from the file size, and the
        // size of the current frame:
        numFramesInFile = fFileSize / (4 + fCurrentFrame.frameSize);
    }

    struct timeval const pt = currentFramePlayTime();
    return numFramesInFile * (pt.tv_sec + pt.tv_usec / (float) MILLION);
}

unsigned MP3StreamState::getByteNumberFromPositionFraction(float fraction) {
    if (fHasXingTOC) {
        // The file is VBR, with a Xing TOC; use it to determine which byte to seek to:
        float percent = fraction * 100.0f;
        unsigned a = (unsigned) percent;
        if (a > 99) a = 99;

        unsigned fa = fXingTOC[a];
        unsigned fb;
        if (a < 99) {
            fb = fXingTOC[a + 1];
        } else {
            fb = 256;
        }
        fraction = (fa + (fb - fa) * (percent - a)) / 256.0f;
    }

    return (unsigned) (fraction * fFileSize);
}

void MP3StreamState::seekWithinFile(unsigned seekByteNumber) {
    if (fFidIsReallyASocket) return; // it's not seekable

    SeekFile64(fFid, seekByteNumber, SEEK_SET);
}

unsigned MP3StreamState::findNextHeader(struct timeval &presentationTime) {
    presentationTime = fNextFramePresentationTime;

    if (!findNextFrame()) return 0;

    // From this frame, figure out the *next* frame's presentation time:
    struct timeval framePlayTime = currentFramePlayTime();
    if (fPresentationTimeScale > 1) {
        // Scale this value
        unsigned secondsRem = framePlayTime.tv_sec % fPresentationTimeScale;
        framePlayTime.tv_sec -= secondsRem;
        framePlayTime.tv_usec += secondsRem * MILLION;
        framePlayTime.tv_sec /= fPresentationTimeScale;
        framePlayTime.tv_usec /= fPresentationTimeScale;
    }
    fNextFramePresentationTime.tv_usec += framePlayTime.tv_usec;
    fNextFramePresentationTime.tv_sec
            += framePlayTime.tv_sec + fNextFramePresentationTime.tv_usec / MILLION;
    fNextFramePresentationTime.tv_usec %= MILLION;

    return fr().hdr;
}

Boolean MP3StreamState::readFrame(unsigned char *outBuf, unsigned outBufSize,
                                  unsigned &resultFrameSize,
                                  unsigned &resultDurationInMicroseconds) {
    /* We assume that "mp3FindNextHeader()" has already been called */

    resultFrameSize = 4 + fr().frameSize;

    if (outBufSize < resultFrameSize) {
#ifdef DEBUG_ERRORS
        fprintf(stderr, "Insufficient buffer size for reading input frame (%d, need %d)\n",
            outBufSize, resultFrameSize);
#endif
        if (outBufSize < 4) outBufSize = 0;
        resultFrameSize = outBufSize;

        return False;
    }

    if (resultFrameSize >= 4) {
        unsigned &hdr = fr().hdr;
        *outBuf++ = (unsigned char) (hdr >> 24);
        *outBuf++ = (unsigned char) (hdr >> 16);
        *outBuf++ = (unsigned char) (hdr >> 8);
        *outBuf++ = (unsigned char) (hdr);

        memmove(outBuf, fr().frameBytes, resultFrameSize - 4);
    }

    struct timeval const pt = currentFramePlayTime();
    resultDurationInMicroseconds = pt.tv_sec * MILLION + pt.tv_usec;

    return True;
}

void MP3StreamState::getAttributes(char *buffer, unsigned bufferSize) const {
    char const *formatStr
            = "bandwidth %d MPEGnumber %d MPEGlayer %d samplingFrequency %d isStereo %d playTime %d isVBR %d";
    unsigned fpt = (unsigned) (filePlayTime() + 0.5); // rounds to nearest integer
#if defined(IRIX) || defined(ALPHA) || defined(_QNX4) || defined(IMN_PIM) || defined(CRIS)
    /* snprintf() isn't defined, so just use sprintf() - ugh! */
    sprintf(buffer, formatStr,
        fr().bitrate, fr().isMPEG2 ? 2 : 1, fr().layer, fr().samplingFreq, fr().isStereo,
        fpt, fIsVBR);
#else
    snprintf(buffer, bufferSize, formatStr,
             fr().bitrate, fr().isMPEG2 ? 2 : 1, fr().layer, fr().samplingFreq, fr().isStereo,
             fpt, fIsVBR);
#endif
}

// This is crufty old code that needs to be cleaned up #####
#define HDRCMPMASK 0xfffffd00

Boolean MP3StreamState::findNextFrame() {
    unsigned char hbuf[8];
    unsigned l;
    int i;
    int attempt = 0;

    read_again:
    if (readFromStream(hbuf, 4) != 4) return False;

    fr().hdr = ((unsigned long) hbuf[0] << 24)
               | ((unsigned long) hbuf[1] << 16)
               | ((unsigned long) hbuf[2] << 8)
               | (unsigned long) hbuf[3];

#ifdef DEBUG_PARSE
    fprintf(stderr, "fr().hdr: 0x%08x\n", fr().hdr);
#endif
    if (fr().oldHdr != fr().hdr || !fr().oldHdr) {
        i = 0;
        init_resync:
#ifdef DEBUG_PARSE
        fprintf(stderr, "init_resync: fr().hdr: 0x%08x\n", fr().hdr);
#endif
        if ((fr().hdr & 0xffe00000) != 0xffe00000
            || (fr().hdr & 0x00060000) == 0 // undefined 'layer' field
            || (fr().hdr & 0x0000F000) == 0 // 'free format' bitrate index
            || (fr().hdr & 0x0000F000) == 0x0000F000 // undefined bitrate index
            || (fr().hdr & 0x00000C00) == 0x00000C00 // undefined frequency index
            || (fr().hdr & 0x00000003) != 0x00000000 // 'emphasis' field unexpectedly set
                ) {
            /* RSF: Do the following test even if we're not at the
           start of the file, in case we have two or more
           separate MP3 files cat'ed together:
            */
            /* Check for RIFF hdr */
            if (fr().hdr == ('R' << 24) + ('I' << 16) + ('F' << 8) + 'F') {
                unsigned char buf[70 /*was: 40*/];
#ifdef DEBUG_ERRORS
                fprintf(stderr,"Skipped RIFF header\n");
#endif
                readFromStream(buf, 66); /* already read 4 */
                goto read_again;
            }
            /* Check for ID3 hdr */
            if ((fr().hdr & 0xFFFFFF00) == ('I' << 24) + ('D' << 16) + ('3' << 8)) {
                unsigned tagSize, bytesToSkip;
                unsigned char buf[1000];
                readFromStream(buf, 6); /* already read 4 */
                tagSize =
                        ((buf[2] & 0x7F) << 21) + ((buf[3] & 0x7F) << 14) + ((buf[4] & 0x7F) << 7) +
                        (buf[5] & 0x7F);
                bytesToSkip = tagSize;
                while (bytesToSkip > 0) {
                    unsigned bytesToRead = sizeof buf;
                    if (bytesToRead > bytesToSkip) {
                        bytesToRead = bytesToSkip;
                    }
                    readFromStream(buf, bytesToRead);
                    bytesToSkip -= bytesToRead;
                }
#ifdef DEBUG_ERRORS
                fprintf(stderr,"Skipped %d-byte ID3 header\n", tagSize);
#endif
                goto read_again;
            }
            /* give up after 20,000 bytes */
            if (i++ < 20000/*4096*//*1024*/) {
                memmove(&hbuf[0], &hbuf[1], 3);
                if (readFromStream(hbuf + 3, 1) != 1) {
                    return False;
                }
                fr().hdr <<= 8;
                fr().hdr |= hbuf[3];
                fr().hdr &= 0xffffffff;
#ifdef DEBUG_PARSE
                fprintf(stderr, "calling init_resync %d\n", i);
#endif
                goto init_resync;
            }
#ifdef DEBUG_ERRORS
            fprintf(stderr,"Giving up searching valid MPEG header\n");
#endif
            return False;

#ifdef DEBUG_ERRORS
            fprintf(stderr,"Illegal Audio-MPEG-Header 0x%08lx at offset 0x%lx.\n",
                fr().hdr,tell_stream(str)-4);
#endif
            /* Read more bytes until we find something that looks
           reasonably like a valid header.  This is not a
           perfect strategy, but it should get us back on the
           track within a short time (and hopefully without
           too much distortion in the audio output).  */
            do {
                attempt++;
                memmove(&hbuf[0], &hbuf[1], 7);
                if (readFromStream(&hbuf[3], 1) != 1) {
                    return False;
                }

                /* This is faster than combining fr().hdr from scratch */
                fr().hdr = ((fr().hdr << 8) | hbuf[3]) & 0xffffffff;

                if (!fr().oldHdr)
                    goto init_resync;       /* "considered harmful", eh? */

            } while ((fr().hdr & HDRCMPMASK) != (fr().oldHdr & HDRCMPMASK)
                     && (fr().hdr & HDRCMPMASK) != (fr().firstHdr & HDRCMPMASK));
#ifdef DEBUG_ERRORS
            fprintf (stderr, "Skipped %d bytes in input.\n", attempt);
#endif
        }
        if (!fr().firstHdr) {
            fr().firstHdr = fr().hdr;
        }

        fr().setParamsFromHeader();
        fr().setBytePointer(fr().frameBytes, fr().frameSize);

        fr().oldHdr = fr().hdr;

        if (fr().isFreeFormat) {
#ifdef DEBUG_ERRORS
            fprintf(stderr,"Free format not supported.\n");
#endif
            return False;
        }

#ifdef MP3_ONLY
        if (fr().layer != 3) {
#ifdef DEBUG_ERRORS
          fprintf(stderr, "MPEG layer %d is not supported!\n", fr().layer);
#endif
          return False;
        }
#endif
    }

    if ((l = readFromStream(fr().frameBytes, fr().frameSize))
        != fr().frameSize) {
        if (l == 0) return False;
        memset(fr().frameBytes + 1, 0, fr().frameSize - 1);
    }

    return True;
}

static Boolean socketIsReadable(int socket) {
    const unsigned numFds = socket + 1;
    fd_set rd_set;
    FD_ZERO(&rd_set);
    FD_SET((unsigned) socket, &rd_set);
    struct timeval timeout;
    timeout.tv_sec = timeout.tv_usec = 0;

    int result = select(numFds, &rd_set, NULL, NULL, &timeout);
    return result != 0; // not > 0, because windows can return -1 for file sockets
}

static char watchVariable;

static void checkFunc(void * /*clientData*/) {
    watchVariable = ~0;
}

static void waitUntilSocketIsReadable(UsageEnvironment &env, int socket) {
    while (!socketIsReadable(socket)) {
        // Delay a short period of time before checking again.
        unsigned usecsToDelay = 1000; // 1 ms
        env.taskScheduler().scheduleDelayedTask(usecsToDelay,
                                                (TaskFunc *) checkFunc, (void *) NULL);
        watchVariable = 0;
        env.taskScheduler().doEventLoop(&watchVariable);
        // This allows other tasks to run while we're waiting:
    }
}

unsigned MP3StreamState::readFromStream(unsigned char *buf,
                                        unsigned numChars) {
    // Hack for doing socket I/O instead of file I/O (e.g., on Windows)
    if (fFidIsReallyASocket) {
        intptr_t fid_long = (intptr_t) fFid;
        int sock = (int) fid_long;
        unsigned totBytesRead = 0;
        do {
            waitUntilSocketIsReadable(fEnv, sock);
            int bytesRead
                    = recv(sock, &((char *) buf)[totBytesRead], numChars - totBytesRead, 0);
            if (bytesRead < 0) return 0;

            totBytesRead += (unsigned) bytesRead;
        } while (totBytesRead < numChars);

        return totBytesRead;
    } else {
#ifndef _WIN32_WCE
        waitUntilSocketIsReadable(fEnv, (int) fileno(fFid));
#endif
        return fread(buf, 1, numChars, fFid);
    }
}

#define XING_FRAMES_FLAG       0x0001
#define XING_BYTES_FLAG        0x0002
#define XING_TOC_FLAG          0x0004
#define XING_VBR_SCALE_FLAG    0x0008

void MP3StreamState::checkForXingHeader() {
    // Look for 'Xing' in the first 4 bytes after the 'side info':
    if (fr().frameSize < fr().sideInfoSize) return;
    unsigned bytesAvailable = fr().frameSize - fr().sideInfoSize;
    unsigned char *p = &(fr().frameBytes[fr().sideInfoSize]);

    if (bytesAvailable < 8) return;
    if (p[0] != 'X' || p[1] != 'i' || p[2] != 'n' || p[3] != 'g') return;

    // We found it.
    fIsVBR = True;

    u_int32_t flags = (p[4] << 24) | (p[5] << 16) | (p[6] << 8) | p[7];
    unsigned i = 8;
    bytesAvailable -= 8;

    if (flags & XING_FRAMES_FLAG) {
        // The next 4 bytes are the number of frames:
        if (bytesAvailable < 4) return;
        fNumFramesInFile = (p[i] << 24) | (p[i + 1] << 16) | (p[i + 2] << 8) | (p[i + 3]);
        i += 4;
        bytesAvailable -= 4;
    }

    if (flags & XING_BYTES_FLAG) {
        // The next 4 bytes is the file size:
        if (bytesAvailable < 4) return;
        fFileSize = (p[i] << 24) | (p[i + 1] << 16) | (p[i + 2] << 8) | (p[i + 3]);
        i += 4;
        bytesAvailable -= 4;
    }

    if (flags & XING_TOC_FLAG) {
        // Fill in the Xing 'table of contents':
        if (bytesAvailable < XING_TOC_LENGTH) return;
        fHasXingTOC = True;
        for (unsigned j = 0; j < XING_TOC_LENGTH; ++j) {
            fXingTOC[j] = p[i + j];
        }
        i += XING_TOC_FLAG;
        bytesAvailable -= XING_TOC_FLAG;
    }
}
